package org.example.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;

/**
 * 与Java NIO的ByteBuffer相比，ByteBuf的优势如下：
 * <ul style="list-style-type:disc">
 * <li>Pooling（池化，这点减少了内存复制和GC，提升了效率）</li>
 * <li>复合缓冲区类型，支持零复制</li>
 * <li>不需要调用filp()方法去切换读写模式</li>
 * <li>扩展性好，例如StringBuffer</li>
 * <li>可以自定义缓冲区类型</li>
 * <li>读取和写入索引分开</li>
 * <li>方法的链式调用</li>
 * <li>可以进行引用计数，方便重复使用</li>
 * </ul>
 * <p>
 * 逻辑部分：
 * ByteBuf是一个字节容器，内部是一个字节数组。从逻辑上来分，字节容器可以分为四个部分。
 * 第一部分已用字节，表示已经使用完的废弃的无效字节；
 * 第二部分是刻度字节，这部分数据是ByteBuf保存的有效数据，从ByteBuf中读取的数据都来自这一部分；
 * 第三部分是可写字节，写入到ByteBuf的数据都会写到这一部分中；
 * 第四部分是可扩容字节，表示的是该ByteBuf最多还能扩容的大小。
 * <p>
 * 重要属性：
 * 通过三个整型的属性有效地区分可读数据和可写数据，使得读写之间相互没有冲突。
 * 这三个属性定义在AbstractByteBuf抽象类中，分别是：
 * readerIndex（读指针）：分开第一部分和第二部分
 * 指示读取的起始位置。每读取一个字节，readerIndex自动增加1。一旦readIndex与writeIndex相等，则表示ByteBuf不可读了。
 * <p>
 * writeIndex（写指针）：分开第二部分和第三部分
 * 指示写入的起始位置。每写一个字节，writeIndex自动增加1.一旦readerIndex与capacity()相等，则表示ByteBuf已经不可写了。
 * capacity()是一个成员方方法，不是一个成员属性，它表示ByteBuf中可以写入的容量。注意，它不是最大容量maxCapacity。
 * <p>
 * maxCapacity（最大容量）：在第四部分最后
 * 表示ByteBuf可以孔融大的最大容量。当向ByteBuf写数据的时候，如果容量不足，则可以进行扩容。
 * 扩容的最大限限度由maxCapacity的值来设定，超过maxCapacity就会报错。
 * <p>
 * capacity：除第四部分之外的容量之和
 */
public class NettyByteBuf {
    /**
     * 什么是池化的ByteBuf缓冲区呢？
     * 在通信程序的执行过程中，Bufer缓冲区会被频繁创建、使用、释放。
     * 大家都知道，频繁创建对象、内存分配、释放内存，
     * 系统的开销大、性能低，如何提升性能、提高Buffer实例的使用率呢？
     * 从Netty4版本开始，新增了对象池化的机制，
     * 对创建一个Buffer，将没有被引用的Buffer对象，放入对象缓存池中；
     * 当需要时，则重新从对象缓存池中取出，而不需要重新创建。
     * <p>
     * 引用计数大致规则如下：
     * 在默认情况下，当创建完一个ByteBuf时，它的引用为1；
     * 每次调用retain()方法，它的引用就加1；
     * 每次调用relase()方法，它的引用就减1；
     * 如果引用为0，再次访问这个对象，将会抛出异常；
     * 如果引用为0，表示这个ByteBuf没有那个进程引用它，它占用的内存需要回收。
     */
    @Test
    public void test1() {
        /**
         * 创建一个ByteBug
         * 说明
         * 1.创建对象，该对象包含一个数组arr，是一个byte[10]
         * 2.在netty的buffer中，不需要使用flip进行反装，底层通过维护readerindex和writeindex来实现
         * 3.通过readerindex和writeindex和capacity，将buffer分成三个区域
         * 0--readerIndex，已经读取的区域
         * readerIndex--writeIndex，可读区域
         * writeIndex--capacity，可写区域
         *
         * 一个连接占用1MB左右的内存空间
         *
         */
        ByteBuf buffer = Unpooled.buffer(10);
        for (int i = 0; i < 10; i++) {
            buffer.writeByte(i);
        }
        System.out.println("capascity=" + buffer.capacity());//10
        //输出
        //如果写入9个，但是写入10个，那么就会出现java.lang.IndexOutOfBoundsException: readerIndex(9) + length(1) exceeds writerIndex(9)
        for (int i = 0; i < buffer.capacity(); i++) {
            System.out.println(buffer.readByte());
        }
        System.out.println("执行完毕");
    }

    @Test
    public void test2() {
        //创建ByteBuf
        ByteBuf buffer = Unpooled.copiedBuffer("Hello,World!", StandardCharsets.UTF_8);
        //使用相关方法
        if (buffer.hasArray()) {
            byte[] content = buffer.array();
            //将其装换为字符串
            System.out.println(new String(content, StandardCharsets.UTF_8));
            //byteBuf=UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 12, cap: 64)
            System.out.println("byteBuf=" + buffer);
            System.out.println(buffer.arrayOffset());//0
            System.out.println(buffer.readerIndex());//0
            System.out.println(buffer.writerIndex());//12
            System.out.println(buffer.readableBytes());//12，可读字节数
            System.out.println("-----");
            int len = buffer.readableBytes();
            for (int i = 0; i < len; i++) {
                System.out.println((char) buffer.readByte());
            }
            System.out.println(buffer.getCharSequence(0, 4, StandardCharsets.UTF_8));
            System.out.println(buffer.getCharSequence(4, 6, StandardCharsets.UTF_8));
        }
    }
}
